<?php
/*
 * 通用文件存储服务管理类
 */
require_once dirname(__FILE__) . '/common.inc.php';

class File_server
{
	public function __construct()
	{
	}

	//查看文件目录清单
	public function listdir($dir_path)
	{
		$result = is_dir($dir_path) ? scandir($dir_path) : false;
		return $result;
	}

	//将$_FILES中上传成功的文件上传到$to_path目录下
	public function upload($to_path, $max_size = 4)
	{
		$result = array();
		is_dir($to_path) or @mkdir($to_path);
		if (is_dir($to_path) && is_writable($to_path))
		{
			foreach ($_FILES as $file)
			{
				if (is_array($file['error']))
				{
					foreach ($file['error'] as $key => $error)
					{
						if ($error == UPLOAD_ERR_OK)
						{
							if($file['size'][$key] > $max_size * 1024 * 1024)	//上传4M限制
							{
								$result[] = false;	
							}
							else
							{
								$dest_name = $to_path . $file['name'][$key];
								if ($result[] = @move_uploaded_file($file['tmp_name'][$key], $dest_name))
								{
									$this->save_update_log('文件上传：' . $dest_name, 'upload');
								}
								else
								{
									$this->update_error("无法将文件{$file['tmp_name'][$key]}保存为{$dest_name}");
								}
							}
						}
						else
						{
							$result[] = $error;
							$msg = $this->file_upload_error_msg($error);
							$this->update_error($msg . '，文件：' . $file['name'][$key]);
						}
					}
				}
				else
				{
					if ($file['error'] == UPLOAD_ERR_OK)
					{ 
						if($file['size'] > $max_size * 1024 * 1024)	// 上传4M限制
						{
							$result[] = false;	
						}
						else
						{
							$dest_name = $to_path . $file['name'];
							if ($result[] = @move_uploaded_file($file['tmp_name'], $dest_name))
							{
								$this->save_update_log('文件上传：' . $dest_name, 'upload');
							}
							else
							{
								$this->update_error("无法将文件{$file['tmp_name']}保存为{$dest_name}");
							}
						}
					}
					else
					{
						$result[] = $file['error'];
						$msg = $this->file_upload_error_msg($file['error']);
						$this->update_error($msg . '，文件：' . $file['name']);
					}
				}
			}
		}
		else
		{
			$result[] = false;
			$this->update_error("文件存储目录{$to_path}不存在或没有写入的权限");
		}
		return $result;
	}

	//pdf文件转为jpg格式
	public function pdf2jpg($pdf_path, $to_path, $filename, $page = -1)
	{
		$return = array();
		$check_flag = true; //是否检测图片已存在
		if (!is_dir($to_path))
		{
			@mkdir($to_path);
			$check_flag = false; //不检测图片是否存在，直接生成新图片
		}
		if(!extension_loaded('imagick'))
		{
			$this->update_error("imagick扩展不存在");
			return false;
		}
		if(!file_exists($pdf_path) || !is_readable($pdf_path))
		{
			$this->update_error("没有指定转换的pdf文件，或不可读");
			return false;
		}
		$to_new_flag = false; //检测图片的过程中，图片是否完整，不完整则生成新图片，默认为完整
		if ($check_flag)
		{
			//先检测该pdf对应已生成的图片是否存在
			$max_page = $this->getpdfpagetotal($pdf_path);
			if ($max_page)
			{
				//循环检测每个图片是否存在
				for ($i = 0; $i < $max_page; $i++)
				{
					$temp_check = $to_path . substr($filename, 0, strlen($filename)-4) . '_' . $i . '.jpg';
					if (!file_exists($temp_check))
					{
						//需要重新生成新图片返回，否则直接返回已存在所有图片
						$to_new_flag = true;
						break;
					}
					else
					{
						$return[] = substr($filename, 0, strlen($filename)-4) . '_' . $i . '.jpg';
					}
				}
			}
			else
			{
				return false;
			}
		}

		if (!$check_flag || ($check_flag && $to_new_flag))
		{
			$return = array();
			$im = new Imagick();
			$im->setResolution(120,120);
			$im->setCompressionQuality(100);

			if ($page == -1)
			{
				$im->readImage($pdf_path);
			}
			else
			{
				$im->readImage($pdf_path."[".$page."]");
			}
			foreach ($im as $key => $var)
			{
				$var->setImageFormat('jpg');
				$newfilename = $to_path . substr($filename, 0, strlen($filename)-4) . '_' . $key . '.jpg';
				if($var->writeImage($newfilename) == true)
				{
					$return[] = substr($filename, 0, strlen($filename)-4) . '_' . $key . '.jpg';
				}
			}
		}
		return $return;
	}
	
	//获取pdf总页数
	protected function getpdfpagetotal($check_pdf)
	{
		if (!$fp = @fopen($check_pdf,"r")) 
		{
			$this->update_error("打开文件{$check_pdf}失败");
			return false;
		}
		else 
		{
			$max = 0;
			while(!feof($fp)) 
			{
				$line = fgets($fp, 255);
				if (preg_match('/\/Count [0-9]+/', $line, $matches))
				{
					preg_match('/[0-9]+/',$matches[0], $matches2);
					if ($max < $matches2[0]) $max = $matches2[0];
				}
			}
			fclose($fp);
			//返回总页数
			return $max;
		}
	}

	//删除指定路径的文件
	public function delete($file_path)
	{
		$result = false;
		if (is_file($file_path))
		{
			if (is_writable($file_path))
			{
				if ($result = @unlink($file_path))
				{
					$this->save_update_log('文件删除：' . $file_path, 'delete');
				}
				else
				{
					$this->update_error('文件删除失败：' . $file_path);
				}
			}
			else
			{
				$this->update_error('无删除权限，无法删除：' . $file_path);
			}
		}
		else
		{
			$result = 1;
			$this->update_error('文件不存在，无法删除：' . $file_path);
		}
		return $result;
	}

	//将指定路径的文件$from_file_path改名或移到新的路径$to_file_path，成功返回true，失败返回false
	public function rename($from_file_path, $to_file_path)
	{
		$result = false;
		if (is_file($from_file_path))
		{
			if (is_writable($from_file_path))
			{
				if ($result = @rename($from_file_path, $to_file_path))
				{
					$this->save_update_log('文件改名/移动：' . $from_file_path . ' => ' . $to_file_path, 'rename');
				}
				else
				{
					$this->update_error('文件改名/移动失败：' . $from_file_path . ' => ' . $to_file_path);
				}
			}
			else
			{
				$this->update_error('无写权限，无法改名/移动：' . $from_file_path . ' => ' . $to_file_path);
			}
		}
		else
		{
			$this->update_error('源路径不是文件或者不存在，无法改名/移动：' . $from_file_path . ' => ' . $to_file_path);
		}
		return $result;
	}

	//获取文件扩展名，并自动转化为小写字母
	public function file_get_ext_name($filename)
	{
		$pos = strrpos($filename, '.');
		$result = ($pos === false) ? '' : strtolower(substr($filename, $pos));
		return $result;
	}

	//查看指定路径的文件
	public function view($file_path)
	{
		$result = false;
		if(file_exists($file_path) && is_readable($file_path))
		{
			$file_size = filesize($file_path);
			if ($file_size < 20 * 1024 * 1024)
			{
				$file_ext = $this->file_get_ext_name($file_path);
				switch ($file_ext)
				{
					case '.jpeg':
					case '.jpg':
						$content_type ='image/jpeg';
						$fread = 1;
						break;
					case '.png':
						$content_type ='image/png';
						$fread = 1;
						break;
					case '.pdf':
						$content_type ='application/pdf';
						$fread = 1;
						break; 
					case '.html':
						$content_type ='text/html; charset=UTF-8';
						$fread = 1;
						break;
                    case '.mp4':
                        $content_type = 'video/mp4';
                        $fread = 1;
                        break;
					case '.txt':
					default:
						$content_type ='text/plain';
						$fread = 0;
						break;
				}
				header('Content-type: ' . $content_type);
				@$this->save_view_log('文件查看：' . $file_path, 'view');
				if ($fread)
				{
					header('Accept-Ranges: bytes');
					header('Accept-Length: ' . $file_size);
					$fp = @fopen($file_path, 'r');
					echo fread($fp, $file_size);
					@fclose($fp);
				}
				else
				{
					echo @file_get_contents($file_path);
				}
				$result = true;
			}
			else
			{
				$this->view_error('文件超过20M，不支持在线查看：' . $file_path);
			}
		}
		else
		{
			$this->view_error('文件查看失败：' . $file_path);
		}
		return $result;
	}

	//查看图片
	public function view_image($file_path, $width = 1024, $height = 1024)
	{
		$result = false;
		if(file_exists($file_path) && is_readable($file_path))
		{
			$file_size = filesize($file_path);
			if ($file_size < 20 * 1024 * 1024)
			{
				$image_info = getimagesize($file_path);
				if ($image_info)
				{
					$image_type = $image_info[2];
					if ($image_type == IMAGETYPE_JPEG)
					{
						$image = imagecreatefromjpeg($file_path);
					}
					elseif ($image_type == IMAGETYPE_GIF)
					{
						$image = imagecreatefromgif($file_path);
					}
					elseif ($image_type == IMAGETYPE_PNG)
					{
						$image = imagecreatefrompng($file_path);
						imagesavealpha($image,true);//设置标记以在保存 PNG 图像时保存完整的 alpha 通道信息
					}
					else
					{
						$image = '';
					}
					if ($image)
					{
						$ori_width = imagesx($image);
						$ori_height =imagesy($image);

						if(!$width || !$height)
						{
							$width=$ori_width;
							$height=$ori_height;
						}
						else
						{
							$ratio_w = $width / $ori_width;
							$ratio_h = $height / $ori_height;
							if($ratio_w<=$ratio_h && $ratio_w<1)
							{
								$height = $ori_height * $ratio_w;
							}
							elseif($ratio_h<$ratio_w && $ratio_h<1)
							{
								$width = $ori_width * $ratio_h;
							}
							else
							{
								$width=$ori_width;
								$height=$ori_height;
							}
						}
						while(!($new_image = @imagecreatetruecolor($width, $height)))
						{
							$width = $width / 2; $height = $height / 2;
						}
						if($image_type == IMAGETYPE_PNG){
							imagealphablending($new_image,false);//允许在真彩色图像上使用两种不同的绘画模式
							imagesavealpha($new_image,true);//设置标记以在保存 PNG 图像时保存完整的 alpha 通道信息
						}
						imagecopyresampled($new_image, $image, 0, 0, 0, 0, $width, $height, $ori_width, $ori_height);				
						header('Cache-Control: max-age=86400,must-revalidate'); 
						header('Last-Modified: ' .gmdate('D, d M Y H:i:s') . ' GMT' ); 
						header("Expires: " .gmdate ('D, d M Y H:i:s', time() + '86400' ). 'GMT'); 
						header("Access-Control-Allow-Origin:*");
						if ($image_type == IMAGETYPE_JPEG)
						{
							header('Content-Type: image/jpeg');
							imagejpeg($new_image);
						}
						elseif ($image_type == IMAGETYPE_GIF)
						{
							header('Content-Type: image/gif');
							imagegif($new_image);
						}
						elseif ($image_type == IMAGETYPE_PNG)
						{
							header('Content-Type: image/png');
							imagepng($new_image);
						}
						else
						{
							header('Content-Type: image/png');
							imagepng($new_image);
						}
						imagedestroy($image);
						imagedestroy($new_image);
						$this->save_view_log('文件查看：' . $file_path, 'view_image');
						$result = true;
					}
					else
					{

						$this->view_error('查看失败，无法imagecreate：' . $file_path);
					}
				}
				else
				{
					$this->view_error('文件不是图片：' . $file_path);
				}
			}
			else
			{
				$this->view_error('文件20M，不支持在线查看：' . $file_path);
			}
		}
		else
		{
			$this->view_error('文件查看失败：' . $file_path);
		}
		die;
		return $result;
	}

	//下载指定路径的文件
	public function download($file_path)
	{
		$result = false;
		if(file_exists($file_path) && is_readable($file_path))
		{
			$file_ext = $this->file_get_ext_name($file_path);
			$file_size = filesize($file_path);
			$fp = fopen($file_path, 'r');
			header("Content-type: application/octet-stream");
			header("Accept-Ranges: bytes");
			if ($file_ext == '.mp4')
			{
				header('Content-type: ' . 'video/mpeg4');
				//header('Content-Transfer-Encoding: mp4');
			}
			header("Accept-Length: " . $file_size);
			header("Content-Disposition: attachment; filename=" . basename($file_path));
			echo fread($fp, $file_size);
			fclose($fp);
			$this->save_view_log('文件下载：' . $file_path, 'download');
			exit;
		}
		else
		{
			$this->view_error('文件下载失败：' . $file_path);
		}
		return $result;
	}

	//获取错误码
	protected function file_upload_error_msg($error_code)
	{
		switch ($error_code)
		{
			case 0:
				$result = '没有错误发生，文件上传成功';
				break;
			
			case 1:
				$result = '上传的文件超过了php.ini中upload_max_filesize 选项限制的值';
				break;

			case 2:
				$result = '上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值';
				break;

			case 3:
				$result = '文件只有部分被上传';
				break;

			case 4:
				$result = '没有文件被上传';
				break;

			case 5:
				$result = '上传文件大小为0';
				break;

			default:
				$result = '未知错误码：' . $error_code;
				break;
		}
		return $result;
	}

	//写操作的成功日志，操作为$operation
	protected function save_update_log($msg, $operation)
	{
		$result = false;
		$file = UPDATE_LOG_ROOT_PATH . 'update_success_' . date('Y-m-d') . '.log';
		if ($fp = @fopen($file, 'a'))
		{
			$log = date('Y-m-d H:i:s') . ', ' . $operation . ', ' . $msg . ', ' . "\n";
			flock($fp, LOCK_EX);
			$result = @fwrite($fp, $log);
			flock($fp, LOCK_UN);
			fclose($fp);
		}
		return $result;
	}

	//写操作的报错日志
	protected function update_error($msg)
	{
		$result = false;
		$file = ERROR_ROOT_PATH . 'update_error_' . date('Y-m-d') . '.log';
		if ($fp = @fopen($file, 'a'))
		{
			$log = date('Y-m-d H:i:s') . ', ' . $msg . ', ' . "\n";
			$result = @fwrite($fp, $log);
			fclose($fp);
		}
		return $result;
	}

	//读操作的成功日志，操作为$operation：0为查看，1为下载
	protected function save_view_log($msg, $operation)
	{
		$result = false;
		$file = VIEW_LOG_ROOT_PATH . 'view_success_' . date('Y-m-d') . '.log';
		if ($fp = @fopen($file, 'a'))
		{
			$log = date('Y-m-d H:i:s') . ', ' . $operation . ', ' . $msg . ', ' . "\n";
			flock($fp, LOCK_EX);
			$result = @fwrite($fp, $log);
			flock($fp, LOCK_UN);
			fclose($fp);
		}
		return $result;
	}

	//读操作的报错日志
	protected function view_error($msg)
	{
		$result = false;
		$file = ERROR_ROOT_PATH . 'view_error_' . date('Y-m-d') . '.log';
		if ($fp = @fopen($file, 'a'))
		{
			$log = date('Y-m-d H:i:s') . ', ' . $msg . ', ' . "\n";
			$result = @fwrite($fp, $log);
			fclose($fp);
		}
		return $result;
	}

	//查看音频
    public function view_audio($file_path)
	{
        $result = false;
        if(file_exists($file_path) && is_readable($file_path))
		{
			$file_ext = $this->file_get_ext_name($file_path);
            $file_size = filesize($file_path);
            if ($file_size < 256 * 1024 * 1024)
			{
                header("Content-type: {$file_size['mime']}");
                @$this->save_view_log('文件查看：' . $file_path, 'view');
                if ($fread)
				{
                    header('Accept-Ranges: bytes');
                    header('Accept-Length: ' . $file_size);
                    $fp = @fopen($file_path, 'r');
                    echo fread($fp, $file_size);
                    @fclose($fp);
                }
				else
				{
					$fp = fopen($file_path, 'r');
					header("Content-type: application/octet-stream");
					header("Accept-Ranges: bytes");
					header("Accept-Length: " . $file_size);
					header("Content-Disposition: attachment; filename=" . basename($file_path));
					echo fread($fp, $file_size);
					@fclose($fp);
                   //echo @file_get_contents($file_path);
                }
                $result = true;
            }
			else
			{
                $this->view_error('音频文件超过256M，不支持在线查看：' . $file_path);
            }
        }
		else
		{
            $this->view_error('文件查看失败：' . $file_path);
        }
        return $result;
    }

	//查看视频
    public function view_video($file_path)
	{
        $result = false;
        if(file_exists($file_path) && is_readable($file_path))
		{
            $file_ext = $this->file_get_ext_name($file_path);
            $file_size = filesize($file_path);
            if ($file_size < 256 * 1024 * 1024)
			{
                header('Content-type: video/' . ltrim($file_ext, '.'));
                @$this->save_view_log('文件查看：' . $file_path, 'view');
                if (1)
				{
                    header('Accept-Ranges: bytes');
                    header('Accept-Length: ' . $file_size);
                    $fp = @fopen($file_path, 'r');
                    echo fread($fp, $file_size);
                    @fclose($fp);
                }
				else
				{
                    echo @file_get_contents($file_path);
                }
                $result = true;
            }
			else
			{
                $this->view_error('视频文件超过256M，不支持在线查看：' . $file_path);
            }
        }
		else
		{
            $this->view_error('文件查看失败：' . $file_path);
        }
        return $result;
    }

    /**
     * 2021年1月15日
     * 浏览器播放
     * @param $file_path
     */
    public function playVideo($file_path){
		$max_size =  300 * 1024 * 1024; //最大播放限制：300m
		$file_ext_list = array('mp4',); //可以播放的视频拓展

		if (!is_file($file_path) || !is_readable($file_path)) {
			$this->view_error('文件查看失败：' . $file_path);
			return false;
		}
		$file_size = filesize($file_path);
		if ($file_size > $max_size) {
			$this->view_error('文件超过300M，不支持在线查看：' . $file_path);
			return false;
		}

		list($file_name, $file_ext) = explode('.', basename($file_path));
		if (!in_array($file_ext, $file_ext_list)) {
			$this->view_error($file_ext . '类型不支持：' . $file_path);
			return false;
		}
		
		header("Content-type:video/{$file_ext}");
		header('Accept-Ranges: bytes');
		
		if(!isset($_SERVER['HTTP_RANGE'])){//首次http请求处理
			header('Accept-Length: ' . $file_size);
			$this->save_view_log('文件查看：' . $file_path. '，首次http请求', 'playVideo');
			return false;
		}

		//分段请求处理
		if(empty($_SERVER['HTTP_RANGE'])){
			$this->view_error('HTTP_RANGE内容 为空，不支持分段文件查看：' . $file_path);
			return false;
		}
		list($type, $scope) = explode('=', $_SERVER['HTTP_RANGE']);
		if(empty($scope)){
			$this->view_error('HTTP_RANGE的范围 为空，不支持分段文件查看：' . $file_path);
			return false;
		}
		list($start, $end) = explode('-', $scope);
		if(isset($start) && $start >= 0){
			$start = (int)$start;
		}else{
			$start = 0;
		}
		if(isset($end) && $end > 0){
			$end = (int)$end;
		}else{
			$end = $file_size - 1;
		}
		if($start > $end){
			$this->view_error("HTTP_RANGE的范围 {$start} > {$end}，不支持分段文件查看：" . $file_path);
			return false;
		}
		
		header('HTTP/1.1 206 Partial Content');
		header('Accept-Length: ' . ($end - $start + 1));
		header("Content-Range:bytes {$start}-{$end}/{$file_size}");

		$step = 1024 * 1024 * 2; //每次获取视频的步长

		if(1){
			$fd = fopen($file_path, 'rb');
			fseek($fd, $start);
			while(!feof($fd)) {
				echo fread($fd, $step);
			}
			fclose($fd);
		}else{
			while($start - $step <= $end){
				echo file_get_contents($file_path,false,null,$start,$step);
				$start += $step;
			}
		}
		$this->save_view_log("文件查看：{$file_path} ，分段http请求，范围：{$start}-{$end}/{$file_size}", 'playVideo');
		return true;
	}
}
